﻿// <copyright file="FlowDocumentCreator.cs" company="Microsoft Corporation">
// Copyright (c) 2008 All Right Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2008-10-10</date>
// <summary>Flow document creator</summary>

namespace Synergist.WordToXamlLibrary
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Forms;
    using System.Windows.Input;
    using System.Windows.Markup;
    using System.Xml;
    using System.Xml.Xsl;
    using Word = Microsoft.Office.Interop.Word;

    /// <summary>
    /// Class to create WPF FlowDocuments
    /// </summary>
    public class FlowDocumentCreator
    {
        #region Fields
        /// <summary>
        /// flow document options
        /// </summary>
        private FlowDocumentOptions options;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the FlowDocumentCreator class.
        /// </summary>
        /// <param name="options">creation options</param>
        /// <exception cref="ArgumentNullException">if options is null</exception>
        public FlowDocumentCreator(FlowDocumentOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException("options");
            }

            this.options = options;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the page padding units
        /// </summary>
        private string PagePaddingUnits
        {
            get
            {
                switch (this.options.PagePaddingUnits)
                {
                    case WordToXamlLibrary.PagePaddingUnits.Pixels:
                        return "px";

                    case WordToXamlLibrary.PagePaddingUnits.Inches:
                        return "in";
                }

                throw new InvalidOperationException("Invalid Page Padding Units.");
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Show XAML using the preview document
        /// </summary>
        /// <param name="xaml">valid XAML</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "XmlTextReader does not implement IDisposable.")]
        public static void ShowXaml(string xaml)
        {
            PreviewDocument preview = new PreviewDocument();

            var reader = new XmlTextReader(xaml, XmlNodeType.Document, null);

            FlowDocument flowDocument = XamlReader.Load(reader) as FlowDocument;

            preview.Content = flowDocument;

            preview.ShowDialog();
        }

        /// <summary>
        /// Create a new flow document asking the user for the filename with a FileName box
        /// </summary>
        /// <param name="document">the Word 2007 document</param>
        [CLSCompliant(false)]
        public void Create(Word.Document document)
        {
            using (System.Windows.Forms.SaveFileDialog dialog = new System.Windows.Forms.SaveFileDialog())
            {
                dialog.FileName = System.IO.Path.ChangeExtension(document.Name, Properties.Resources.FileExtension);
                dialog.Filter = Properties.Resources.Filters;
                dialog.DefaultExt = Properties.Resources.FileExtension;
                dialog.Title = Properties.Resources.SaveAsTitle;

                if (dialog.ShowDialog() == DialogResult.OK)
                {
                    System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.WaitCursor;

                    using (CreatingWpf progressForm = new CreatingWpf(4))
                    {
                        progressForm.Load += delegate(object loadSender, EventArgs loadEventArgs)
                        {
                            progressForm.StepProgress(Properties.Resources.LoadingTransform);

                            try
                            {
                                this.CreateXaml(document.WordOpenXML, dialog.FileName, progressForm);

                                progressForm.StepProgress(Properties.Resources.Done);
                            }
                            catch (System.IO.IOException ioe)
                            {
                                MessageBox.Show(ioe.Message, "Error Creating XAML File");

                                return;
                            }
                            catch (System.Xml.Xsl.XsltException xslte)
                            {
                                MessageBox.Show(xslte.Message, "Error Transforming XML");

                                return;
                            }
                            finally
                            {
                                progressForm.Close();
                            }
                        };

                        progressForm.ShowDialog();
                    }

                    System.Windows.Forms.Cursor.Current = System.Windows.Forms.Cursors.Default;

                    System.Diagnostics.Process.Start(dialog.FileName);
                }
            }
        }

        /// <summary>
        /// Create a XAML document from WordprocessingML
        /// </summary>
        /// <param name="wordOpenXml">Microsoft Office OpenXML WordprocessingML</param>
        /// <param name="fileName">XAML filename</param>
        /// <param name="progressForm">progress form interface</param>
        public void CreateXaml(string wordOpenXml, string fileName, IProgress progressForm)
        {
            System.Xml.Xsl.XslCompiledTransform transform = CreateTransform(this.options.XamlType);

            System.Xml.XmlDocument doc = new XmlDocument();

            if (progressForm != null)
            {
                progressForm.StepProgress(Properties.Resources.GettingWordOpenXml);
            }

            doc.LoadXml(wordOpenXml);

            ////System.Diagnostics.Trace.WriteLine(doc.InnerXml);

            XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8);

            // We don't indent the formatting because Paragraph Run elements insert spaces between runs.
#if DEBUG
            writer.Indentation = 4;
            writer.Formatting = Formatting.Indented;
#endif

            if (progressForm != null)
            {
                progressForm.StepProgress(Properties.Resources.Transforming);
            }

            string directory = System.IO.Path.GetDirectoryName(fileName);

            var arguments = this.SetArguments(new Imaging(directory));

            transform.Transform(doc, arguments, writer);

            writer.Close();
        }

        /// <summary>
        /// Create Xaml from a word filename
        /// </summary>
        /// <param name="wordFilename">the filename of a Microsoft Word file</param>
        /// <returns>the WPF XAML representation of the Word Filename</returns>
        public string CreateXaml(string wordFilename)
        {
            var word = new Word.ApplicationClass();

            object refNull = null;
            object falseValue = false;
            object missing = Type.Missing;
            object refFilename = wordFilename;

            var wordDoc = word.Documents.Open(ref refFilename, ref missing, ref falseValue, ref falseValue, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);

            var xml = wordDoc.WordOpenXML;

            wordDoc.Close(ref falseValue, ref refNull, ref refNull);

            var doc = new XmlDocument();

            doc.LoadXml(xml);

            XslCompiledTransform transform = CreateTransform(this.options.XamlType);

            using (var writer = new StringWriter())
            {
                string directory = System.IO.Path.GetDirectoryName(wordFilename);

                var arguments = this.SetArguments(new Imaging(directory));

                transform.Transform(doc, arguments, writer);

                writer.Close();

                writer.Flush();

                return writer.ToString();
            }
        }

        /// <summary>
        /// Show a flow document
        /// </summary>
        /// <param name="wordOpenXml">the word open XML</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "XmlTextReader does not implement IDisposable.")]
        public void ShowFlowDocument(string wordOpenXml)
        {
            if (string.IsNullOrEmpty(wordOpenXml))
            {
                throw new ArgumentException(Properties.Resources.CannontBeNullOrEmpty, "wordOpenXml");
            }

            XslCompiledTransform transform = null;

            try
            {
                transform = CreateTransform(this.options.XamlType);
            }
            catch (System.Xml.Xsl.XsltException xslte)
            {
                var message = string.Format(
                    System.Globalization.CultureInfo.CurrentCulture, 
                    Properties.Resources.MessageLinePosition, 
                    xslte.Message, 
                    xslte.LineNumber, 
                    xslte.LinePosition);

                MessageBox.Show(message, Properties.Resources.ErrorWithTransform);

                return;
            }

            var doc = new XmlDocument();

            doc.LoadXml(wordOpenXml);

            StringWriter textWriter = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);

            XmlTextWriter writer = new XmlTextWriter(textWriter);

            // We don't indent the formatting because Paragraph Run elements insert spaces between runs.
#if DEBUG
            writer.Indentation = 4;
            writer.Formatting = Formatting.Indented;
#endif

            PreviewDocument preview = new PreviewDocument();

            Imaging imaging = null;

            switch (this.options.XamlType)
            {
                case XamlType.SilverlightRichTextBox:
                    imaging = new Imaging(Path.GetTempPath() + @"\WordFlowDocumentCreator Silverlight App\ClientBin");
                    break;

                case XamlType.SilverlightStackPanel:
                    imaging = new Imaging(Path.GetTempPath() + @"\WordFlowDocumentCreator Silverlight App\ClientBin");
                    break;

                case XamlType.WPF:
                    imaging = new Imaging(preview.Resources);
                    break;
            }

            System.Xml.Xsl.XsltArgumentList arguments = this.SetArguments(imaging);

            try
            {
                transform.Transform(doc, arguments, writer);
            }
            catch (System.Xml.Xsl.XsltException xslte)
            {
                MessageBox.Show(xslte.Message, "Error Transforming XML");

                return;
            }

            writer.Close();

            string xaml = textWriter.GetStringBuilder().ToString();

            if (this.options.XamlType == XamlType.WPF)
            {
                PreviewWPF(preview, xaml);
            }
            else
            {
                PreviewSilverlight(xaml);
            }
        }
        #endregion

        #region Implementation

        /// <summary>
        /// Preview Silverlight on a web page
        /// </summary>
        /// <param name="xaml">the Silverlight XAML</param>
        private static void PreviewSilverlight(string xaml)
        {
            var tempDirectory = Path.GetTempPath() + @"\WordFlowDocumentCreator Silverlight App";

            if (!Directory.Exists(tempDirectory))
            {
                Directory.CreateDirectory(tempDirectory);
            }

            using (System.IO.StreamWriter writer = new StreamWriter(tempDirectory + @"\Normal.css"))
            {
                writer.WriteLine(Properties.Resources.Normal);
                writer.Flush();
            }

            using (var writer = new StreamWriter(tempDirectory + @"\SilverlightHandling.js"))
            {
                writer.WriteLine(Properties.Resources.SilverlightHandling);
                writer.Flush();
            }

            System.Xml.XmlDocument doc = new XmlDocument();
            
            doc.Load(new StringReader(Properties.Resources.preview));

            var xamlText = doc.GetElementsByTagName("textarea")[0];

            xamlText.InnerText = xaml;

            var previewHtml = tempDirectory + @"\preview.html";

            doc.Save(previewHtml);

            if (!Directory.Exists(tempDirectory + @"\ClientBin"))
            {
                Directory.CreateDirectory(tempDirectory + @"\ClientBin");
            }

            using (var file = File.Create(tempDirectory + @"\ClientBin\PreviewSilverlight.xap"))
            {
                file.Write(Properties.Resources.PreviewSilverlight, 0, Properties.Resources.PreviewSilverlight.Length);

                file.Flush();
            }

            System.Diagnostics.Process.Start(previewHtml);
        }

        /// <summary>
        /// Preview the WPF document
        /// </summary>
        /// <param name="preview">the preview document form</param>
        /// <param name="xaml">the WPF XAML</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "XmlTextReader does not implement IDisposable.")]
        private static void PreviewWPF(PreviewDocument preview, string xaml)
        {
            System.Diagnostics.Debug.WriteLine(xaml);

            XmlTextReader reader = new XmlTextReader(xaml, XmlNodeType.Document, null);
            
            try
            {
                FlowDocument flowDocument = XamlReader.Load(reader) as FlowDocument;

                preview.Content = flowDocument;

                System.Collections.IDictionaryEnumerator enumerator = preview.Resources.GetEnumerator();
                enumerator.MoveNext();

                preview.ShowDialog();
            }
            catch (XamlParseException xpe)
            {
                System.Diagnostics.Debug.WriteLine(xpe.Message);

                MessageBox.Show(xpe.Message, Properties.Resources.InvalidXAMLGenerated);
            }
        }

        /// <summary>
        /// Create the transformation
        /// </summary>
        /// <param name="xamlType">the XAML Type</param>
        /// <returns>a new XSL transformation</returns>
        /// <exception cref="System.Xml.Xsl.XsltException">If the XSL transform fails to load successfully.</exception>
        private static XslCompiledTransform CreateTransform(XamlType xamlType)
        {
            XslCompiledTransform transform = new XslCompiledTransform(true);

            string xslt = string.Empty;

            switch (xamlType)
            {
                case XamlType.WPF:
                    xslt = Properties.Resources.WordMLtoWPF;
                    break;

                case XamlType.SilverlightRichTextBox:
                    xslt = Properties.Resources.WordMLtoSilverlightRichTextBox;
                    break;

                case XamlType.SilverlightStackPanel:
                    xslt = Properties.Resources.WordMLtoSilverlightStackPanel;
                    break;
            }

            var settings = new XsltSettings()
            {
                EnableDocumentFunction = true,
                EnableScript = true,
            };

            using (StringReader input = new StringReader(xslt))
            {
                ////var reader = new XmlTextReader(input);

                var readerSettings = new XmlReaderSettings()
                {
                    ValidationType = ValidationType.Schema,
                    ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.ReportValidationWarnings
                };

                var reader = XmlReader.Create(input, readerSettings);

                var resolver = new ResourceStreamResolver();

                transform.Load(reader, settings, resolver);
            }

            return transform;
        }

        /// <summary>
        /// Set the extension arguments for the XSL Transformation
        /// </summary>
        /// <param name="imaging">the imaging extension object</param>
        /// <returns>the XSLT argument list</returns>
        private System.Xml.Xsl.XsltArgumentList SetArguments(Imaging imaging)
        {
            System.Xml.Xsl.XsltArgumentList arguments = new System.Xml.Xsl.XsltArgumentList();

            arguments.AddExtensionObject("http://blogs.msdn.com/synergist/imaging", imaging);

            arguments.AddExtensionObject("urn:utilities", new TransformUtilities());

            this.SetArguments(arguments);

            return arguments;
        }

        /// <summary>
        /// Set the transformation arguments
        /// </summary>
        /// <param name="arguments">the argument list</param>
        private void SetArguments(XsltArgumentList arguments)
        {
            arguments.AddParam("IsOptimalParagraphEnabled", string.Empty, this.options.IsOptimalParagraphEnabled);
            arguments.AddParam("IsHyphenationEnabled", string.Empty, this.options.IsHyphenationEnabled);
            arguments.AddParam("IsColumnWidthFlexible", string.Empty, this.options.IsColumnWidthFlexible);
            arguments.AddParam("DropShadowSoftness", string.Empty, this.options.DropShadowSoftness);
            arguments.AddParam("DropShadowNoise", string.Empty, this.options.DropShadowNoise);

            string pagePadding = "Auto";
            if (!this.options.AutoPagePadding)
            {
                pagePadding = this.options.PagePadding.ToString(System.Globalization.CultureInfo.InvariantCulture) + this.PagePaddingUnits;
            }

            arguments.AddParam("PagePadding", string.Empty, pagePadding);

            string columnWidth = "Auto";

            if (!this.options.AutoColumnWidth)
            {
                columnWidth = this.options.ColumnWidth.ToString(System.Globalization.CultureInfo.InvariantCulture) + this.options.ColumnWidthUnits;
            }

            arguments.AddParam("ColumnWidth", string.Empty, columnWidth);
        }
        #endregion
    }
}
